package com.lsiyun.sync_file.app;

import com.lsiyun.sync_file.Utils;
import com.lsiyun.sync_file.protocol.SyncFile;
import com.lsiyun.sync_file.protocol.SyncMessage;
import com.lsiyun.sync_file.protocol.SyncProtocol;

import java.io.*;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class Client {

    public static final String SERVER_HOST = "localhost";
    public static final int PORT = 8888;
    public static final String SYNC_DIR_CLIENT = "F:/sync/client/";
    private static final File SYNC_DIR = new File(SYNC_DIR_CLIENT);

    /**
     * 链接服务器的Socket
     */
    private Socket socket;

    public Client(String host, int port) throws IOException {
        socket = linkServer();
    }

    public static void main(String[] args) {

        while (true) {
            try {
                System.out.println("开始同步");
                Client client = new Client(SERVER_HOST, PORT);
                SyncFile syncInfo = client.requestSyncFileInfo();
                client.sync(new SyncFile(SYNC_DIR.getPath(), ""), syncInfo);
                System.out.println("结束同步");

                synchronized (client) {
                    client.wait(1000 * 10);
                }
            } catch (Exception e) {
                System.out.println("同步失败");
                System.out.println(e.getMessage());
                e.printStackTrace();
            }
        }
    }

    /**
     * 同步
     *
     * @param localFile  本地的同步文件
     * @param remoteFile 远程的同步文件
     */
    private void sync(SyncFile localFile, SyncFile remoteFile) throws Exception {
        // 如果不同名，不需要继续判断
        if (!Objects.equals(localFile.getPath(), remoteFile.getPath())) {
            return;
        }

        // 如果修改时间一致，且MD5相同，表示文件是相同
        if (Objects.equals(localFile.getUpdateTime(), remoteFile.getUpdateTime())
                && Objects.equals(localFile.getMd5(), remoteFile.getMd5())) {
            return;
        }

        // 如果都是文件夹，进行文件夹同步
        if (localFile.isDir() && remoteFile.isDir()) {
            syncDir(localFile, remoteFile);
            return;
        }

        if (localFile.getUpdateTime() > remoteFile.getUpdateTime()) {
            request(SyncMessage.generateMessage4C2S_DELETE(remoteFile));
            request(SyncMessage.generateMessage4C2S_ADD(localFile));
        } else if (localFile.getUpdateTime() < remoteFile.getUpdateTime()) {
            if (new File(SYNC_DIR, localFile.getPath()).delete()) {
                request(SyncMessage.generateMessage4C2S_GET_FILE(remoteFile));
            }
        } else {
            // TODO 更新时间相同
        }
    }

    /**
     * 同步目录
     *
     * @param localDir  本地的同步文件夹
     * @param remoteDir 远程的同步文件夹信息
     */
    private void syncDir(SyncFile localDir, SyncFile remoteDir) throws Exception {
        if (Objects.equals(localDir.getUpdateTime(), remoteDir.getUpdateTime())) {
            return;
        }

        List<SyncFile> localFiles = localDir.getFiles();
        List<SyncFile> remoteFiles = remoteDir.getFiles();

        Map<String, SyncFile> remoteMap = new HashMap<>();
        for (SyncFile localFile : localFiles) {
            boolean had = false;
            for (SyncFile remoteFile : remoteFiles) {
                if (Objects.equals(localFile.getPath(), remoteFile.getPath())) {
                    had = true;
                    sync(localFile, remoteFile);
                    remoteMap.put(remoteFile.getPath(), remoteFile);
                    break;
                }
            }
            if (!had) {
                request(SyncMessage.generateMessage4C2S_ADD(new SyncFile(SYNC_DIR_CLIENT, localFile.getPath())));
            }
        }
        for (SyncFile remoteFile : remoteFiles) {
            if (!remoteMap.containsKey(remoteFile.getPath())) {
                if (remoteFile.isFile()) {
                    request(SyncMessage.generateMessage4C2S_GET_FILE(new SyncFile(SYNC_DIR_CLIENT, remoteFile.getPath())));
                } else if (remoteFile.isDir()) {
                    new File(SYNC_DIR, remoteFile.getPath()).mkdirs();
                }
            }
        }

        // 修改目录时间
        if (localDir.getUpdateTime() > remoteDir.getUpdateTime()) {
            request(SyncMessage.generateMessage4C2S_UPDATE_TIME(new SyncFile(SYNC_DIR_CLIENT, remoteDir.getPath())));
        } else {
            var file = new File(SYNC_DIR, remoteDir.getPath());
            file.setLastModified(remoteDir.getUpdateTime());
        }
    }

    private static Socket linkServer() throws IOException {
        return new Socket(SERVER_HOST, PORT);
    }

    private SyncFile requestSyncFileInfo() throws Exception {
        request(SyncMessage.generateMessage4C2S_GetSyncFileInfo());
        return (SyncFile) receiveMessage();
    }

    /**
     * 接受服务器信息
     *
     * @return
     */
    private Object receiveMessage() throws IOException, ClassNotFoundException {
        ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
        Object res = inputStream.readObject();
        return res;
    }

    /**
     * 给服务器发送信息
     */
    private void request(SyncMessage message) throws Exception {
        ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
        outputStream.writeObject(message);
        // switch(message.getType()){
        //     case SyncProtocol.C2S_ADD_FILE:
        //         FileInputStream fileInputStream = new FileInputStream(new File(SYNC_DIR, message.getSyncFile().getPath()));
        //         Utils.transData(fileInputStream, outputStream);
        //         break;
        // }
        if (message.getType() == SyncProtocol.C2S_ADD_FILE) {
            if (message.getSyncFile().isFile()) {
                FileInputStream fileInputStream = new FileInputStream(new File(SYNC_DIR, message.getSyncFile().getPath()));
                Utils.transData(fileInputStream, outputStream);
                fileInputStream.close();
            }
        } else if (message.getType() == SyncProtocol.C2S_GET_FILE) {
            if (message.getSyncFile().isFile()) {
                var file = new File(SYNC_DIR, message.getSyncFile().getPath());
                FileOutputStream fileOutputStream = new FileOutputStream(file);
                Utils.transData(socket.getInputStream(), fileOutputStream);
                file.setLastModified(message.getSyncFile().getUpdateTime());
                fileOutputStream.close();
            } else if (message.getSyncFile().isDir()) {
                new File(SYNC_DIR, message.getSyncFile().getPath()).mkdirs();
            }
        }
    }

}
